# -*- coding:utf-8 -*-
__author__ = 'xigu007'
__all__ = ('StaggeredViewMixin', 'StaggeredLayoutManager',)
from collections import deque
from recycleview import RecycleViewMixin, RecycleLayoutManager

class StaggeredViewMixin(RecycleViewMixin):
    def __init__(self,**kwargs):
        self.recycleview = None
        self._changed_flag = True #view的高度是否已经改变
        self._index = -1
        super(StaggeredViewMixin, self).__init__(**kwargs)
        self.size_hint_y = None

    @property
    def Index(self):
        return self._index

    @Index.setter
    def Index(self,value):
        self._index = value

    @property
    def Changed(self):
        return self._changed_flag

    @Changed.setter
    def Changed(self,value):
        self._changed_flag = value

    def refresh_view_attrs(self, rv, data):
        #重新设置view属性时需要重新计算高度，changed_flag设置为True
        self._changed_flag = True
        super(StaggeredViewMixin, self).refresh_view_attrs(rv, data)

    def compute_height(self, recycleview, index, view_width):
        #子类重载，有时候view的高度经常会随着数据的变化会变化，比如网上加载图片，块文件都会改变iew的高度
        #有时候希望view保持宽高比例，当宽度改变时高度就随之变化
        return self.height

    def notify_height_changed_to_manager(self):
        #如果view的高度发生变化通知layoutmanager
        self._changed_flag = True
        #print 'height_changed = {}'.format(self._index)
        if self.recycleview:
            layout_manager = self.recycleview.layout_manager
            layout_manager.refresh_view_height(self._index)

class StaggeredLayoutManager(RecycleLayoutManager):
    """StaggeredGridLayoutManager是交错排版，可以实现瀑布流排版。只能上下滚动。
    所有的view可以不是固定高度。比如从网上加载图片，事先不知道大小，块文字不知道有多高。
    view的高度随之里面的显示内容的大小变化而变化
    """
    reach_bottom = False
    reach_top = False
    view_port_bottom = 0
    view_port_top = 0
    view_bottom = 0
    view_top = 0
    start_index = 0
    visible_views_index = deque()

    def refresh_view_height(self, view_index):
        recycleview = self.recycleview
        adapter = recycleview.adapter
        views = adapter.views
        if views.has_key(view_index):
            recycleview._handle_ask_data_refresh(extent='data_size')

    def refresh_view_from_start_index(self, index):
        recycleview = self.recycleview
        adapter = recycleview.adapter
        num = len(adapter.data)
        if 0 <= index < num:
            self.visible_views_index.clear()
            self.start_index = index
            recycleview._handle_ask_data_refresh(extent='data_size')


    def compute_positions_and_sizes(self, append):
        """当data发送变化时，当前显示views的index全部更改，并且清空dirtview
        这里不会重新计算positions和sizes
        """
        recycleview = self.recycleview
        container = recycleview.container
        container.width = recycleview.width
        self.reach_bottom = False
        self.reach_top = False
        self.clear_layout()
        adapter = recycleview.adapter
        adapter.make_views_dirty()
        dirty_views = adapter.dirty_views
        for _key, instances in dirty_views.items():
            for _index, view in instances.items():
                view.Changed = True
        #保证容器高度不小于view高度，以便可以滑动
        if container.height < recycleview.height:
            container.height = recycleview.height + 2
        self.view_port_top, self.view_port_bottom = self._get_view_port_bottom_top(recycleview.height, container.height, recycleview.scroll_y)
        visible_views_index = self.visible_views_index
        #从上往下重新布局views
        if self.view_top < self.view_port_top:
            self.view_top = self.view_port_top
        if visible_views_index:
            self.start_index = visible_views_index[0]
        else:
            self.view_top = self.view_port_top
            if self.start_index == 0:
                self._modify_container_h_from_top(recycleview, (self.view_top - container.height))
        self.view_bottom = self.view_top
        self.view_port_bottom += 20  #强制上拉一下，重新布局
        self.visible_views_index.clear()

    def _reset_manager(self, recycleview):
        self.view_top = 0
        self.view_bottom = 0
        self.start_index = 0
        self.visible_views_index.clear()
        adapter = recycleview.adapter
        if adapter:
            adapter.invalidate()
        container = recycleview.container
        container.height = 0
        self._modify_container_h_from_bottom(recycleview, recycleview.height + 2)

    def recycleview_setup(self):
        """
        StaggeredGridLayoutManager只能上下滚动
        """
        self.orientation = "vertical"
        recycleview = self.recycleview
        recycleview.do_scroll_x = False
        recycleview.do_scroll_y = True
        self._reset_manager(recycleview)

    def _get_view_port_bottom_top(self, secycleview_h, container_h, scroll_y):
        px_bottom = max(0, (container_h - secycleview_h) * scroll_y)
        px_top = px_bottom + min(secycleview_h, container_h)
        return px_top, px_bottom

    def compute_visible_views(self):
        recycleview = self.recycleview
        adapter = recycleview.adapter
        container = recycleview.container
        #if recycleview.scroll_y < 0.0 or recycleview.scroll_y > 1.0:
            #print '-----over scroll = {}'.format(recycleview.scroll_y)
        #    return
        new_view_port_top, new_view_port_bottom  = self._get_view_port_bottom_top(recycleview.height, container.height, recycleview.scroll_y)
        max_bottom = self.view_bottom
        min_top = self.view_top
        new = []
        if new_view_port_bottom  > self.view_port_bottom:
            '''正在向下滚动
           '''
            self.view_port_top = new_view_port_top
            self.view_port_bottom  = new_view_port_bottom
            if self.view_port_top >= min_top:
                new = self._expanding_top(adapter, recycleview)
            old = self._reducing_bottom(adapter)

        else:
            '''正在向上滚动
           '''
            self.view_port_top = new_view_port_top
            self.view_port_bottom  = new_view_port_bottom
            if self.view_port_bottom < max_bottom:
                new = self._expanding_bottom(adapter, recycleview)
            old = self._reducing_top(adapter)

        rm = container.remove_widget
        for widget in old:
            rm(widget)

        add = container.add_widget
        for widget in new:
            # add to the container if it's not already done
            if widget.parent is None:
                add(widget)

    def _modify_container_h_from_bottom(self, recycleview, diff_h):
        """
        :return:
        """
        diff_h = int(diff_h)
        if 1 > abs(diff_h):
            print 'modify height from bottom, but not changed'
            return
        container = recycleview.container
        sw = (container.height + diff_h - recycleview.height)
        #保证容器高度不小于view高度，以便可以滑动
        if sw <  2:
            sw = 2
            diff_h = 2 + recycleview.height - container.height
            print 'container too short, modify dff_h to{}'.format(diff_h)
        self.view_bottom  += diff_h
        self.view_top += diff_h
        self.view_port_bottom += diff_h
        self.view_port_top += diff_h
        if container.height + diff_h - self.view_port_bottom  < recycleview.height:
            self.view_port_bottom = container.height + diff_h - recycleview.height
            print '----modify vp_b={}'.format(self.view_port_bottom)
        adapter = recycleview.adapter
        views = adapter.views
        for index, view in views.items():
            view.y += diff_h
        new_scroll_y = self.view_port_bottom / float(sw)
        recycleview.scroll_y = min(1, max(new_scroll_y, 0))
        print '---bottom- new_scroll_y={}'.format(recycleview.scroll_y)
        container.height += diff_h


    def _modify_container_h_from_top(self, recycleview, diff_h):
        diff_h = int(diff_h)
        if 1 > abs(diff_h):
            print 'modify height from top, but not changed'
            return
        container = recycleview.container
        sw = (container.height + diff_h - recycleview.height)
        if sw <  2:
            diff_h = 2 + recycleview.height - container.height
            sw = 2
            print 'container too short, modify dff_h to{}'.format(diff_h)
        #if sw >= 1:
        new_scroll_y = self.view_port_bottom / float(sw)
        recycleview.scroll_y = min(1, max(new_scroll_y, 0))
        print '---top- new_scroll_y={}'.format(new_scroll_y)
        container.height += diff_h

    def _expanding_bottom(self, adapter, recycleview):
        new_views = []
        container = recycleview.container
        visible_views = adapter.views
        visible_views_index = self.visible_views_index
        view_w = container.width
        if len(visible_views_index) > 0:
            next_index = visible_views_index[-1] + 1
        else:
            print '--refresh all views--'
            next_index = self.start_index
        data_len = len(adapter.data)
        max_bottom = self.view_bottom
        while max_bottom >= self.view_port_bottom:
            if next_index < data_len:
                view = adapter.get_view(next_index)
                view.Index = next_index
                view_h = view.height
                if view.Changed:
                    view_h =  view.compute_height(recycleview, next_index, view_w)
                    view.Changed = False
                view_port = (0, container.width, self.view_port_top, self.view_port_bottom)
                view.refresh_view_layout(recycleview, next_index, (0, max_bottom - view_h), (view_w, view_h), view_port)
                visible_views[next_index] = view
                visible_views_index.append(next_index)
                new_views.append(view)
                max_bottom -= view_h
                if max_bottom < 0:
                    #container长高度不够扩展高度
                    diff_h = view_h*(data_len - next_index)
                    self.view_bottom = max_bottom
                    self._modify_container_h_from_bottom(recycleview, diff_h)
                    max_bottom = self.view_bottom
                next_index = next_index + 1
            else:
                #已经到最后一个数据,去掉container多余的高度
                if not self.reach_bottom:
                    diff_h = -max_bottom
                    print 'reach the bottom={}, max_bottom={}'.format(self.view_port_bottom, max_bottom)
                    self.view_bottom = max_bottom
                    self._modify_container_h_from_bottom(recycleview, diff_h)
                    max_bottom = self.view_bottom
                    self.reach_bottom = True
                else:
                    break
        self.view_bottom = max_bottom
        return new_views


    def _expanding_top(self, adapter, recycleview):
        new_views = []
        container = recycleview.container
        visible_views = adapter.views
        view_top = self.view_top
        visible_views_index = self.visible_views_index
        view_w = container.width
        trace = self.start_index
        while view_top <= self.view_port_top:
            if trace > 0:
                trace -= 1
                view = adapter.get_view(trace)
                view.Index = trace
                view_h = view.height
                if view.Changed:
                    view_h =  view.compute_height(recycleview, trace, view_w)
                    view.Changed = False
                view_port = (0, container.width, self.view_port_top, self.view_port_bottom)
                view.refresh_view_layout(recycleview,trace,(0, view_top), (view_w, view_h), view_port)
                last_view_h = view.height
                visible_views[trace] = view
                visible_views_index.appendleft(trace)
                new_views.append(view)
                view_top += last_view_h
                if view_top > container.height:
                    #container长高度不够扩展高度
                    diff_h = last_view_h*trace
                    self.view_top = view_top
                    self._modify_container_h_from_top(recycleview, diff_h)
                    view_top = self.view_top
                if 0 == trace:
                    #已经到第一个数据,去掉container多余的高度
                    if not self.reach_top:
                        print 'reach the top={}'.format(self.view_port_top)
                        self.view_top = view_top
                        self._modify_container_h_from_top(recycleview, (self.view_top - container.height))
                        view_top = self.view_top
                        self.reach_top = True
                    else:
                        break
            else:
                break
        self.start_index = trace
        self.view_top = view_top
        return new_views

    def _reducing_top(self,adapter):
        old_views =[]
        visible_views = adapter.views
        visible_views_index = self.visible_views_index
        make_view_dirty = adapter.make_view_dirty
        index = self.start_index
        view_top = self.view_top
        view = visible_views[index]
        ht = view.height
        while view_top - ht  > self.view_port_top:
            view_top -= ht
            #print '_brc_top view_port_top={}, v_t={}, index={}'.format(self.view_port_top,v_t, index)
            visible_views.pop(index)
            visible_views_index.popleft()
            old_views.append(view)
            make_view_dirty(view, index)
            if not visible_views_index:
                print 'reducing_top visible_views_index is empty'
                break
            view = visible_views[index + 1]
            ht = view.height
            index += 1
        self.view_top = view_top
        self.start_index = index
        return old_views


    def _reducing_bottom(self,adapter):
        old_views =[]
        visible_views = adapter.views
        make_view_dirty = adapter.make_view_dirty
        visible_views_index = self.visible_views_index
        view_bottom = self.view_bottom
        index = visible_views_index[-1]
        view = visible_views[index]
        ht = view.height
        while view_bottom + ht  < self.view_port_bottom:
            view_bottom += ht
            #print '_brc_bottom view_port_top={}, view_bottom={}, index={}'.format(self.view_port_bottom, view_bottom, index)
            visible_views.pop(index)
            visible_views_index.pop()
            old_views.append(view)
            make_view_dirty(view, index)
            if not visible_views_index:
                print 'reducing_bottom visible_views_index is empty'
                break
            index = visible_views_index[-1]
            view = visible_views[index]
            ht = view.height
        self.view_bottom = view_bottom
        return old_views


